/*******************************************************************************
 * Copyright (c) 2010 Oak Ridge National Laboratory.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 ******************************************************************************/
package org.eclipse.nebula.visualization.widgets.figureparts;


import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.xygraph.linearscale.AbstractScale;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;

/**
 * Round scale has the tick labels and tick marks on a circle. 
 * It can be used for any round scale based widget, such meter, gauge, knob etc. <br>
 * A round scale is comprised of Scale line, tick labels and tick marks which include
 * minor ticks and major ticks. <br>
 * The endAngle is on the clockwise side of startAngle. Regardless the startAngle and endAngle,
 * the scale will always be drawn in a square. The bounds will be automatically cropped to the
 * square with the max possible size.
 * 
 * @author Xihui Chen
 *  
 */
public class RoundScale extends AbstractScale {
	
	public static final int SPACE_BTW_MARK_LABEL = 1;
    
    
    /** the scale tick labels */
    private RoundScaleTickLabels tickLabels;

    /** the scale tick marks */
    private RoundScaleTickMarks tickMarks;

    /** the length of the whole scale in pixels */
    private int lengthInPixels;
    
    /** the length of the whole scale in degrees */
    private double lengthInDegrees;
    
    /** The estimated donut width which is used calculate the radius. */
    private int estimatedDonutWidth;
    
    /** the start angle of the scale in degrees, which is the angle position of minimum */
    private double startAngle = 225;
        
    /** the end angle of the scale in degrees, which is the angle position of maximum.
     * The end angle is in the clockwise of startAngle. */
    private double endAngle = 315;
    
    /** the radius of the scale */
    private int radius;  
    
    
//    private final Font DEFAULT_FONT = CustomMediaFactory.getInstance().getFont(
//    		CustomMediaFactory.FONT_ARIAL);
    
    /**
     * Constructor.
     */
    public RoundScale() {      
    	
        tickLabels = new RoundScaleTickLabels(this);        
        tickMarks = new RoundScaleTickMarks(this);      
        setMajorTickMarkStepHint(50);
        add(tickMarks);        
        add(tickLabels);    
//        setFont(DEFAULT_FONT);
 
    }
	
	private void calcEstimatedDonutWidth() {
		estimatedDonutWidth = Math.max(FigureUtilities.getTextExtents(
					format(getRange().getLower()),getFont()).width, 
					FigureUtilities.getTextExtents(format(getRange().getUpper()), getFont()).width)
					+ SPACE_BTW_MARK_LABEL + RoundScaleTickMarks.MAJOR_TICK_LENGTH;
	}
	
	/**
	 * @return the length of the whole scale in pixels
	 */
	public int getLengthInPixels() {
		return lengthInPixels;
	}
	
	/**
	 * @return the length of the whole scale in degrees
	 */
	public double getLengthInDegrees() {
		return lengthInDegrees;
	}
	
	/**@param pixels the pixels to be converted
	 * @return the corresponding length in radians
	 */
	public double convertPixelToRadians(int pixels) {
		return lengthInDegrees * (Math.PI/180) * pixels / lengthInPixels;
	}

	

    /**
     * @return the estimated donut width, which is used to calculate the radius
     */
    public int getEstimatedDonutWidth() {
		if(isDirty())
			calcEstimatedDonutWidth();
		return estimatedDonutWidth;
	}

	@Override
	public Dimension getPreferredSize(int wHint, int hHint) {
	
		wHint = Math.min(wHint, hHint);
		hHint = wHint;
		Dimension size = new Dimension(wHint, hHint);			
		return size;
		
	}

    /**
     * Gets the scale tick labels.
     * 
     * @return the scale tick labels
     */
    public RoundScaleTickLabels getScaleTickLabels() {
        return tickLabels;
    }
    
    /**
     * Gets the scale tick marks.
     * 
     * @return the scale tick marks
     */
    public RoundScaleTickMarks getScaleTickMarks() {
        return tickMarks;
    }

	
    public double getCoercedValuePosition(double value, boolean relative){
		//coerce to range
		double min = getRange().getLower();
        double max = getRange().getUpper();
        if(max>=min)
        	value = value < min ? min : (value > max ? max : value);
        else
        	value = value > min? min: (value<max? max: value);
		return getValuePosition(value, relative);
    }
    
    /**
	 * Get the position of the value in degrees. Which is the angular coordinate in the polar 
	 * coordinate system, whose pole(the origin) is the center of the bounds, whose polar axis 
	 * is from left to right on horizontal if relative is false.    
	 * @param value the value to find its position. It can be value out of range.
	 * @param relative the polar axs would be counterclockwisely rotated to the endAngle if true.
	 * @return position in degrees
	 */
	public double getValuePosition(double value, boolean relative) {
		updateTick();

		double valuePosition;
		if(isLogScaleEnabled()) {
			if(value <=0)
				throw new IllegalArgumentException(
						"Invalid value: value must be greater than 0");
			valuePosition = startAngle - ((Math.log10(value) - Math
                    .log10(min))
                    / (Math.log10(max) - Math.log10(min)) * lengthInDegrees);
		}			
		else			
			valuePosition = startAngle - ((value - min)/(max-min)*lengthInDegrees);
		
		//rotate the axis to endAngle
		if(relative)
			valuePosition  -= endAngle;
		
		if(valuePosition < 0)
			valuePosition += 360;
		
		return valuePosition;
	}

    @Override
    protected void layout() {
    	super.layout();
    	updateTick();
      	Rectangle area = getClientArea();
      	tickLabels.setBounds(area);
      	tickMarks.setBounds(area);
      	
    } 

    @Override
    public void setBounds(Rectangle rect) {
    	if(!bounds.equals(rect))
    		setDirty(true);    	
    	//get the square in the rect
    	rect.width = Math.min(rect.width, rect.height);
    	rect.height = rect.width;
    	super.setBounds(rect);  	
    } 

   
    public void setFont(Font font) {
        if (font != null && font.isDisposed()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        tickLabels.setFont(font);
        super.setFont(font);
        
    }

    public void setForegroundColor(Color color) {
        tickMarks.setForegroundColor(color);
        tickLabels.setForegroundColor(color);
    }
	
	
	

	/**
     * Updates the tick, recalculate all parameters, such as margin, length...
     */
    public void updateTick() {
    	if(isDirty()){      		
    		//set radius
        	if(getTickLabelSide() == LabelSide.Primary) {
        		//set an estimated radius first
        		radius = bounds.width/2 - getEstimatedDonutWidth();        		
        		if(endAngle - startAngle > 0) {
        			lengthInDegrees = 360 - (endAngle - startAngle);
        			lengthInPixels =(int) (2*Math.PI*radius*( 1 - (endAngle-startAngle)/360));
        		}    			
        		else {
        			lengthInDegrees = startAngle - endAngle;
        			lengthInPixels =(int) (2*Math.PI*radius*((startAngle-endAngle)/360));
        		}    	
        		tickLabels.update(lengthInDegrees, lengthInPixels);  
        		
        		//adjust the radius so the tick labels have enough space to 
        		//be drawn inside the bounds  
        		radius -= tickLabels.getTickLabelMaxOutLength();
        	}    	
        	else
        		radius = bounds.width/2 - 1;
        	
    		
    		if(endAngle - startAngle > 0) {
    			lengthInDegrees = 360 - (endAngle - startAngle);
    			lengthInPixels =(int) (2*Math.PI*radius*( 1 - (endAngle-startAngle)/360));
    		}    			
    		else {
    			lengthInDegrees = startAngle - endAngle;
    			lengthInPixels =(int) (2*Math.PI*radius*((startAngle-endAngle)/360));
    		}    	
	    	tickLabels.update(lengthInDegrees, lengthInPixels); 
	    	
	    	setDirty(false);
    	}
    	
    }
	
	/**
     * Updates the tick layout.
     
    protected void updateLayoutData() {
        axisTickLabels.updateLayoutData();
        axisTickMarks.updateLayoutData();
    }
     */
	
	@Override
	protected boolean useLocalCoordinates() {
		return true;
	}

	/**
	 * @param startAngle the startAngle to set
	 */
	public void setStartAngle(double startAngle) {
		this.startAngle = startAngle;
	}

	/**
	 * @return the startAngle
	 */
	public double getStartAngle() {
		return startAngle;
	}

	/**
	 * @param endAngle the endAngle to set
	 */
	public void setEndAngle(double endAngle) {
		this.endAngle = endAngle;
	}

	/**
	 * @return the endAngle
	 */
	public double getEndAngle() {
		return endAngle;
	}

	/**
	 * @param radius the radius to set
	 */
	public void setRadius(int radius) {
		this.radius = radius;
	}

	/**
	 * @return the radius
	 */
	public int getRadius() {			
		return radius;
	}
	
	/**
	 * @return the inner radius for a primary tick label side scale.  
	 */
	public int getInnerRadius() {		
		updateTick();
		return radius;
	}
}
